/*
 * Copyright (c) 2007-2011, Lloyd Hilaiel <lloyd@hilaiel.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "api/yajl_parse.h"
#include "yajl_lex.h"
#include "yajl_parser.h"
#include "yajl_alloc.h"

#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>

const char *
yajl_status_to_string(yajl_status stat)
{
    const char * statStr = "unknown";
    switch (stat) {
        case yajl_status_ok:
            statStr = "ok, no error";
            break;
        case yajl_status_client_canceled:
            statStr = "client canceled parse";
            break;
        case yajl_status_error:
            statStr = "parse error";
            break;
    }
    return statStr;
}

#include <stdio.h>
int debugCommandCount = 0;

// Use this macro only when tracking Binary JSon or Message Pack.
// #define INTERNAL_DEBUG_JSON_PARSER

yajl_callbacks debug_route = {
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL
};

/* bunch of forward declarations */
int log_null(void * ctx);
int log_boolean(void * ctx, int boolVal);
int log_integer(void * ctx, long long integerVal);
int log_double(void * ctx, double doubleVal);
int log_string(void * ctx, const unsigned char * stringVal,
               size_t stringLen, int cte_pool);
int log_startmap(void * ctx, unsigned int size);
int log_endarray(void * ctx);
int log_mapkey(void * ctx, const unsigned char * key,
               size_t stringLen, int cte_pool);
int log_endmap(void * ctx);
int log_startarray(void * ctx, unsigned int size);

int log_null(void * ctx) {
	printf("Command %8i Null\n",debugCommandCount);
	debugCommandCount++;
	return debug_route.yajl_null(ctx);
}

int log_boolean(void * ctx, int boolVal) {
	printf("Command %8i Boolean %i\n",debugCommandCount, boolVal);
	debugCommandCount++;
	return debug_route.yajl_boolean(ctx,boolVal);
}

int log_integer(void * ctx, long long integerVal) {
	printf("Command %8i Integer %lli\n",debugCommandCount, integerVal);
	debugCommandCount++;
	return debug_route.yajl_integer(ctx, integerVal);
}

int log_double(void * ctx, double doubleVal) {
	printf("Command %8i Double %f\n",debugCommandCount, doubleVal);
	debugCommandCount++;
	return debug_route.yajl_double(ctx, doubleVal);
}

int log_string(void * ctx, const unsigned char * stringVal,
                    size_t stringLen, int cte_pool) {
	unsigned int n;
	printf("Command %8i String[%i] '",debugCommandCount, (int)stringLen);
	for (n=0; n<stringLen; n++) {
		printf("%c",stringVal[n]);
	}
	printf("'\n");
	debugCommandCount++;
	return debug_route.yajl_string(ctx, stringVal, stringLen, cte_pool);
}

int log_startmap(void * ctx, unsigned int size) {
	printf("Command %8i StartMap\n",debugCommandCount);
	debugCommandCount++;
	return debug_route.yajl_start_map(ctx, size);
}

int log_mapkey(void * ctx, const unsigned char * key,
                        size_t stringLen, int cte_pool) {
	printf("Command %8i MapKey\n",debugCommandCount);
	debugCommandCount++;
	return debug_route.yajl_map_key(ctx, key, stringLen, cte_pool);
}

int log_endmap(void * ctx) {
	printf("Command %8i EndMap\n",debugCommandCount);
	debugCommandCount++;
	return debug_route.yajl_end_map(ctx);
}

int log_startarray(void * ctx, unsigned int size) {
	printf("Command %8i StartArray\n",debugCommandCount);
	debugCommandCount++;
	return debug_route.yajl_start_array(ctx, size);
}

int log_endarray(void * ctx) {
	printf("Command %8i EndArray\n",debugCommandCount);
	debugCommandCount++;
	return debug_route.yajl_end_array(ctx);
}

yajl_callbacks debugcallbacks = {
	log_null,
	log_boolean,
	log_integer,
	log_double,
	NULL,
	log_string,
	log_startmap,
	log_mapkey,
	log_endmap,
	log_startarray,
	log_endarray
};

yajl_handle
yajl_alloc(const yajl_callbacks * callbacks,
           yajl_alloc_funcs * afs,
           void * ctx)
{
    yajl_handle hand = NULL;
    yajl_alloc_funcs afsBuffer;

    /* first order of business is to set up memory allocation routines */
    if (afs != NULL) {
        if (afs->malloc == NULL || afs->realloc == NULL || afs->free == NULL)
        {
            return NULL;
        }
    } else {
        yajl_set_default_alloc_funcs(&afsBuffer);
        afs = &afsBuffer;
    }

    hand = (yajl_handle) YA_MALLOC(afs, sizeof(struct yajl_handle_t));

    /* copy in pointers to allocation routines */
    memcpy((void *) &(hand->alloc), (void *) afs, sizeof(yajl_alloc_funcs));

#ifndef INTERNAL_DEBUG_JSON_PARSER
	hand->callbacks = callbacks;
	hand->ctx = ctx;
#else
	hand->callbacks		= &debugcallbacks;
	debug_route			= *callbacks;
	hand->ctx			= ctx;
	debugCommandCount	= 0;
#endif
    hand->lexer = NULL; 
    hand->bytesConsumed = 0;
    hand->decodeBuf = yajl_buf_alloc(&(hand->alloc));
    hand->flags	    = 0;
    yajl_bs_init(hand->stateStack, &(hand->alloc));
    yajl_bs_push(hand->stateStack, yajl_state_start);

	//
	// Binary JSon Parser
	//
	hand->bj.afs		= afs;
	hand->bj.callbacks	= callbacks;
	hand->bj.cp			= NULL;
	hand->bj.ctx		= ctx;
	hand->bj.strs		= NULL;
	hand->bj.used		= 0;

    return hand;
}

int
yajl_config(yajl_handle h, yajl_option opt, ...)
{
	int rv = 1;
	if (h->bj.used == 0) {
		va_list ap;
		va_start(ap, opt);

		switch(opt) {
			case yajl_allow_comments:
			case yajl_dont_validate_strings:
			case yajl_allow_trailing_garbage:
			case yajl_allow_multiple_values:
			case yajl_allow_partial_values:
				if (va_arg(ap, int)) h->flags |= opt;
				else h->flags &= ~opt;
				break;
			default:
				rv = 0;
		}
		va_end(ap);
	}

    return rv;
}

void
yajl_free(yajl_handle handle)
{
	//
	// Binary JSon
	//
	if (handle->bj.cp)		YA_FREE(&(handle->alloc), handle->bj.cp);
	if (handle->bj.strs)	YA_FREE(&(handle->alloc), handle->bj.strs);

    yajl_bs_free(handle->stateStack);
    yajl_buf_free(handle->decodeBuf);
    if (handle->lexer) {
        yajl_lex_free(handle->lexer);
        handle->lexer = NULL;
    }
    YA_FREE(&(handle->alloc), handle);
}

yajl_status
yajl_parse(yajl_handle hand, const unsigned char * jsonText,
           size_t jsonTextLen)
{
    yajl_status status;

	if (jsonTextLen >= 2) {
		if (jsonText[0] == 0xFF && jsonText[1] == 0xFF) {
			hand->bj.used = 1;
			return (yajl_status)bjson_parse(hand,jsonText,jsonTextLen);
		} else if ((jsonText[0] >= 0xDC) && (jsonText[0] <= 0xDF)) {
			hand->msgpack.used = 1;
			return (yajl_status)msgpack_parse(hand,jsonText,jsonTextLen);
		}
	}

    /* lazy allocation of the lexer */
    if (hand->lexer == NULL) {
        hand->lexer = yajl_lex_alloc(&(hand->alloc),
                                     hand->flags & yajl_allow_comments,
                                     !(hand->flags & yajl_dont_validate_strings));
    }

    status = yajl_do_parse(hand, jsonText, jsonTextLen);
    return status;
}


yajl_status
yajl_complete_parse(yajl_handle hand)
{
	if (hand->bj.used) {
		return yajl_status_ok;
	}

    /* The lexer is lazy allocated in the first call to parse.  if parse is
     * never called, then no data was provided to parse at all.  This is a
     * "premature EOF" error unless yajl_allow_partial_values is specified.
     * allocating the lexer now is the simplest possible way to handle this
     * case while preserving all the other semantics of the parser
     * (multiple values, partial values, etc). */
    if (hand->lexer == NULL) {
        hand->lexer = yajl_lex_alloc(&(hand->alloc),
                                     hand->flags & yajl_allow_comments,
                                     !(hand->flags & yajl_dont_validate_strings));
    }

    return yajl_do_finish(hand);
}

unsigned char *
yajl_get_error(yajl_handle hand, int verbose,
               const unsigned char * jsonText, size_t jsonTextLen)
{
    return yajl_render_error_string(hand, jsonText, jsonTextLen, verbose);
}

size_t
yajl_get_bytes_consumed(yajl_handle hand)
{
    if (!hand) return 0;
    else return hand->bytesConsumed;
}


void
yajl_free_error(yajl_handle hand, unsigned char * str)
{
    /* use memory allocation functions if set */
    YA_FREE(&(hand->alloc), str);
}

/* XXX: add utility routines to parse from file */
